package com.openwater.tools;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import android.graphics.Bitmap;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.util.Log;

import com.openwater.tools.math.Vector2f;
import com.openwater.tools.math.Vector3f;

public class GLTools10 {

    private static final String TAR = "GLTools10";

    private static int[] mViewPort = new int[4];
    private static float[] mProjectMatrix = new float[16];
    private static float[] mModeViewMatrix = new float[16];

    public static void setViewPort(GL10 gl) {
        gl.glGetIntegerv(GL11.GL_VIEWPORT, mViewPort, 0);
    }

    public static void setModeViewMatrix(GL10 gl) {
        ((GL11) gl).glGetFloatv(GL11.GL_MODELVIEW_MATRIX, mModeViewMatrix, 0);
    }

    public static void setProjectMatrix(GL10 gl) {
        ((GL11) gl).glGetFloatv(GL11.GL_PROJECTION_MATRIX, mProjectMatrix, 0);
    }

    /**
     * Map GL (object) coordinate to screen coordinate
     * 
     * @param glX
     *            object x coordinate
     * @param glY
     *            object y coordinate
     * @param glZ
     *            object z coordinate
     * @return screen 2D coordinate
     */
    public static Vector2f glCoor2WinCoor(float glX, float glY, float glZ) {
        float[] winCoor = new float[3];
        if (GLU.gluProject(glX, glY, glZ, mModeViewMatrix, 0, mProjectMatrix, 0, mViewPort, 0,
                winCoor, 0) == GL10.GL_TRUE) {
            winCoor[0] = mViewPort[2] - winCoor[0];
            return new Vector2f(winCoor[0], winCoor[1]);
        } else {
            Log.w(TAR, "glCoor2WinCoor: Map object coordinates to window coordinates error !");
            return null;
        }
    }

    /***
     * Map screen coordinate to GL object 3D coordinates.
     * 
     * @param winX
     *            x coordinate on screen
     * @param winY
     *            y coordinate on screen
     * @return 3D coordinate x,y,z
     */
    public static Vector3f winCoor2GLCoor(float winX, float winY) {
        final float[] nearVec = new float[4];
        final float[] farVec = new float[4];
        final boolean bNear = GLU.gluUnProject(mViewPort[2] - winX, winY, 0.0f, mModeViewMatrix, 0,
                mProjectMatrix, 0, mViewPort, 0, nearVec, 0) == GL10.GL_TRUE;
        final boolean bFar = GLU.gluUnProject(mViewPort[2] - winX, winY, 1.0f, mModeViewMatrix, 0,
                mProjectMatrix, 0, mViewPort, 0, farVec, 0) == GL10.GL_TRUE;
        if (bNear && bFar) {
            nearVec[0] /= nearVec[3];
            nearVec[1] /= nearVec[3];
            nearVec[2] /= nearVec[3];
            farVec[0] /= farVec[3];
            farVec[1] /= farVec[3];
            farVec[2] /= farVec[3];
            float y_magnitude = Math.abs(farVec[1] - nearVec[1]);
            float near_factor = Math.abs(nearVec[1]) / y_magnitude;
            float far_factor = Math.abs(farVec[1]) / y_magnitude;
            nearVec[0] *= far_factor;
            nearVec[1] *= far_factor;
            nearVec[2] *= far_factor;
            farVec[0] *= near_factor;
            farVec[1] *= near_factor;
            farVec[2] *= near_factor;
            return new Vector3f(nearVec[0] + farVec[0], nearVec[1] + farVec[1], nearVec[2]
                    + farVec[2]);
        } else {
            Log.w(TAR, "winCoor2GLCoor: Map window coordinates to object coordinates error !");
            return null;
        }
    }

    public static void delTexId(GL10 gl, int[] texId) {
        gl.glDeleteTextures(texId.length, texId, 0);
    }

    public static void delTexId(GL10 gl, int texId) {
        gl.glDeleteTextures(1, new int[] { texId }, 0);
    }

    public static int genMipMapTexId(GL10 gl, Bitmap bitmap) {
        // scale the bitmap to power of 2
        int w = nextPowerOfTwo(bitmap.getWidth());
        int h = nextPowerOfTwo(bitmap.getHeight());
        if (bitmap.getWidth() != w || bitmap.getHeight() != h) {
            bitmap = BitmapToolkit.scaleImage(bitmap, w, h);
        }
        // initial texture id
        int[] textures = new int[1];
        gl.glGenTextures(1, textures, 0);
        int currid = textures[0];
        gl.glBindTexture(GL10.GL_TEXTURE_2D, currid);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
                GL10.GL_LINEAR_MIPMAP_NEAREST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
                GL10.GL_LINEAR_MIPMAP_LINEAR);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
        gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE);
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
        return currid;
    }

    public static int genTexId(GL10 gl, Bitmap bitmap) {
        // scale the bitmap to power of 2
        int w = nextPowerOfTwo(bitmap.getWidth());
        int h = nextPowerOfTwo(bitmap.getHeight());
        if (bitmap.getWidth() != w || bitmap.getHeight() != h) {
            bitmap = BitmapToolkit.scaleImage(bitmap, w, h);
        }
        // initial texture id
        int[] textures = new int[1];
        gl.glGenTextures(1, textures, 0);
        int currid = textures[0];
        gl.glBindTexture(GL10.GL_TEXTURE_2D, currid);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();
        return currid;
    }

    public static int genTexId(GL10 gl, ByteBuffer data, int width, int height, int format) {
        int[] textures = new int[1];
        gl.glGenTextures(1, textures, 0);
        int currid = textures[0];
        gl.glBindTexture(GL10.GL_TEXTURE_2D, currid);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        switch (format) {
        case 8: // Alpha channel info only
            gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_ALPHA, width, height, 0, GL10.GL_ALPHA,
                    GL10.GL_UNSIGNED_BYTE, data);
            break;
        case 24: // RGB Texture
            gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGB, width, height, 0, GL10.GL_RGB,
                    GL10.GL_UNSIGNED_BYTE, data);
            break;
        case 32: // RGBA Texture
            gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA, width, height, 0, GL10.GL_RGBA,
                    GL10.GL_UNSIGNED_BYTE, data);
            break;
        }
        return currid;
    }

    public static Bitmap capture(GL10 gl, int x, int y, int w, int h) {
        try {
            final int b[] = new int[w * h];
            final int bt[] = new int[w * h];
            IntBuffer buff = IntBuffer.wrap(b);
            buff.position(0);
            gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, buff);
            for (int i = 0; i < h; i++) {
                for (int j = 0; j < w; j++) {
                    int pix = b[i * w + j];
                    int pb = (pix >> 16) & 0xff;
                    int pr = (pix << 16) & 0x00ff0000;
                    int pix1 = (pix & 0xff00ff00) | pr | pb;
                    bt[(h - i - 1) * w + j] = pix1;
                }
            }
            Bitmap bitmap = Bitmap.createBitmap(bt, w, h, Bitmap.Config.ARGB_8888);
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param x
     *            integer
     * @return TRUE if x is a power of two, FALSE otherwise
     */
    public static boolean isPowerOfTwo(int x) {
        return (x != 0) && ((x & (x - 1)) == 0);
    }

    public static int nextPowerOfTwo(int n) {
        n--;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        n++;
        return n;
    }
}
